import lombok.extern.slf4j.Slf4j;
import configClient.IConfigurationListener;
import configClient.TccConfigClient;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import static java.util.Collections.emptySet;

/**
 * 隶属于 SpringBoot 上下文。监听 ApplicationPreparedEvent 事件
 * (ApplicationPreparedEvent 事件发布的时候 environment 已经准备好）
 * 这个时候会遍历所有tcc属性源，获得他们的 tccConfigClient 然后添加监听器。
 * 监听器在 tcc变更的时候被回调, 回调逻辑检查 key有没有变更, 变更的key发布事件 触发 @Configuration @Value 等刷新
 */

@Configuration
@ConditionalOnClass(TccConfigClient.class)
@AutoConfigureAfter(RefreshAutoConfiguration.class)
@Slf4j
public class TccWatchAutoConfiguration implements ApplicationListener<ApplicationPreparedEvent> {

    // 事件发布器
    private final ApplicationEventPublisher publisher;
    private final BaseTccPropertySourceLocator tccPropertySourceLocator;
    private final RefreshScope refreshScope;

    public TccWatchAutoConfiguration(
            ApplicationEventPublisher publisher,
            BaseTccPropertySourceLocator tccPropertySourceLocator,
            RefreshScope refreshScope
    ) {
        this.publisher = publisher;
        this.tccPropertySourceLocator = tccPropertySourceLocator;
        this.refreshScope = refreshScope;
    }

    @Override
    public void onApplicationEvent(ApplicationPreparedEvent event) {
        // 获得所有tcc属性源
        Collection<BaseTccPropertySource> tccPropertySources = tccPropertySourceLocator.getTccPropertySources();
        for (BaseTccPropertySource tccPropertySource : tccPropertySources) {
            TccConfigClient tccClient = tccPropertySource.getTccConfigClient();
            // 添加监听器
            tccClient.addListener(tccPropertySource.getKey(), new IConfigurationListener() {
                // config 是 key 变更后的内容
                @Override
                public void onLoad(String config, String ori) {
                    try {
                        Set<String> changedKeys = handleChangeEvent(config, tccPropertySource, tccPropertySources);
                        log.info("tcc更新成功, changed keys = " + changedKeys);
                    } catch (Exception e) {
                        log.error("tcc更新失败，" + e.getMessage(), e);
                    }
                }

                @Override
                public String identifier() {
                    return "tcc-plus_" + tccPropertySource.getPsm() + "_"
                            + tccPropertySource.getConfigSpace() + "_"
                            + tccPropertySource.getEnvironment().name() + "_"
                            + tccPropertySource.getKey();
                }
            });
        }
    }

    private Set<String> handleChangeEvent(String configInfo,
                                          BaseTccPropertySource propertySource,
                                          Collection<BaseTccPropertySource> tccPropertySources
    ) {
        Properties newProps;
        try {
            // 新的配置属性
            newProps = propertySource.loadProperties(configInfo);
        } catch (IOException e) {
            log.warn("tcc更新加载失败:" + e.getMessage(), e);
            return emptySet();
        }
        Properties oldProps = propertySource.props();
        // diff
        Map<String, RichEnvironmentChangeEvent.Change> changes = RichEnvironmentChangeEvent.diff(newProps, oldProps);
        if (changes.isEmpty()) {
            // 未发生更改
            return emptySet();
        }
        assertNotDuplicate(newProps, propertySource, tccPropertySources);
        // 更新属性源
        propertySource.props(newProps);
        // 发布更新事件，触发Spring上下文进行重新绑定
        RichEnvironmentChangeEvent event = new RichEnvironmentChangeEvent(changes);
        publisher.publishEvent(event);
        refreshScope.refreshAll();
        return changes.keySet();
    }

    private void assertNotDuplicate(Properties newProps, BaseTccPropertySource old, Collection<BaseTccPropertySource> allTccPropertySources) {
        ModuleConfigDuplicateChecker duplicateChecker = new ModuleConfigDuplicateChecker();
        for (BaseTccPropertySource propertySource : allTccPropertySources) {
            if (propertySource != old) {
                duplicateChecker.assertNotDuplicate(propertySource.getName(), propertySource.getPropertyNames());
            }
        }
        duplicateChecker.assertNotDuplicate(old.getName(), newProps.stringPropertyNames().toArray(new String[0]));
    }
}
